package org.bronycorps.mlptss.domain.actor;

import org.bronycorps.mlptss.domain.controller.AbstractController;
import org.bronycorps.mlptss.domain.layer.Track;
import org.bronycorps.mlptss.domain.model.AbstractActorModel;
import org.bronycorps.mlptss.utils.DrawableAnimation;

import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.ui.Image;

/**
 * Abstract class of the grouped actors. An actor is a sprite to display in the
 * stage. A group actor is a group of sprites. It acts like a view in the MVC
 * aspect. Every actor has an animation that contains all the frames the actor
 * will display. Also, the actor is controlled by a dedicated controller and
 * displays the data of a dedicated model. However, in the workflow of the game,
 * the main manager will call the actor class to refresh and paint itself, and
 * call its controller to update the model position.
 */
public abstract class AbstractGroupActor extends Group {

	/**
	 * current displayed animation
	 */
	protected DrawableAnimation currentAnimation;
	/**
	 * animation's timer, used to know which frame to display
	 */
	protected float currentTime;

	/**
	 * hitbox of the actor
	 */
	protected final Rectangle hitBox = new Rectangle();

	protected Image mainImage;
	protected final Rectangle relativeHitBox = new Rectangle();

	public AbstractGroupActor() {
		super();
		this.mainImage = new Image();
		initializeChildren();
	}

	@Override
	public void act(float delta) {
		super.act(delta);

		getController().act(delta);

		this.currentTime += delta;
		drawFrame();

		updateVisualPosition();
	}

	protected void drawFrame() {
		this.mainImage.setDrawable(this.currentAnimation
				.getDrawableKeyFrame(this.currentTime));
	}

	protected Rectangle getBounds() {
		return this.currentAnimation.getBounds();
	}

	/**
	 * controller of the actor
	 * 
	 * @return controller instance
	 */
	protected abstract AbstractController getController();

	public Rectangle getHitBox() {
		return this.hitBox;
	}

	/**
	 * return the radius of the hitbox as if it was a sphere
	 * 
	 * @return radius of the actor's hitbox
	 */
	public float getHitBoxRadius() {
		return (float) Math.sqrt((this.hitBox.width * this.hitBox.width / 4)
				+ (this.hitBox.height * this.hitBox.height / 4));
	}

	/**
	 * returns the model of the actor
	 * 
	 * @return
	 */
	public abstract AbstractActorModel getModel();

	/**
	 * returns the origin point of the current animation. Used for axe of
	 * rotation of the actor's image.
	 * 
	 * @return position of model of the actor
	 */
	protected Vector2 getModelOrigin() {
		return this.currentAnimation.getOrigin();
	}

	/**
	 * returns the position of the model. It may not be the actual position of
	 * the image of the actor.
	 * 
	 * @return position of model of the actor
	 */
	protected abstract Vector2 getModelPosition();

	public Rectangle getRelativeHitBox() {
		this.relativeHitBox.set(this.hitBox);
		this.relativeHitBox.x = this.relativeHitBox.x - (getX() + getOriginX());
		this.relativeHitBox.y = this.relativeHitBox.y - (getY() + getOriginY());
		return this.relativeHitBox;
	}

	/**
	 * returns the track in which the actor exists
	 * 
	 * @return the track related to the actor
	 */
	public Track getTrack() {
		return (Track) getParent();
	}

	protected void initializeChildren() {
		addActor(this.mainImage);
	}

	@Override
	public void setScale(float scale) {
		super.setScale(scale);
		// reset hitbox from previous scaling, since scale parameter is refering
		// to 1:1
		this.hitBox.set(getBounds());
		// rescale it
		this.hitBox.width *= scale;
		this.hitBox.height *= scale;

		this.hitBox.x = getX() + getOriginX() - scale
				* (getModelOrigin().x - this.hitBox.x);
		this.hitBox.y = getY() + getOriginY() - scale
				* (getModelOrigin().y - this.hitBox.y);

		getRelativeHitBox();
	}

	/**
	 * Change the actor's current animation
	 * 
	 * @param nextAnimation
	 *            animation to set
	 */
	protected void switchAnimation(DrawableAnimation nextAnimation) {
		// reset animation's timer
		this.currentTime = 0;
		// change the animation
		this.currentAnimation = nextAnimation;
		drawFrame();
		// adapt the bounds and size of the actor to the new animation
		setOrigin(getModelOrigin().x, getModelOrigin().y);
		setWidth(this.mainImage.getPrefWidth());
		setHeight(this.mainImage.getPrefHeight());
	}

	protected void updateHitbox() {
		Rectangle bounds = getBounds();
		this.hitBox.x = getX() + getOriginX() - getScaleX()
				* (getModelOrigin().x - bounds.x);
		this.hitBox.y = getY() + getOriginY() - getScaleY()
				* (getModelOrigin().y - bounds.y);

		getRelativeHitBox();

	}

	/**
	 * updates the position of the image to display from the actor, using the
	 * model position and the origin. Updates also the hitbox
	 */
	protected void updateVisualPosition() {
		Vector2 visualPosition = getModelPosition().cpy().sub(getOriginX(),
				getOriginY());
		setPosition(visualPosition.x, visualPosition.y);

		updateHitbox();
	}

}